/**
 * 本模块是由axios的实例创建，不污染axios环境。
 * 同步使用方式	this.ajax.apiname([params])  同步执行，直接赋值
 * 异步使用方式	this.ajax.apiname([params]).then(result=>{}).catch()  在then回调中接受结果
 * 快捷方法		this.ajax(url,[config],[callback],[config])	默认为post方式，config与axios()参数相同  有callback时为异步方法，无callback时调用流程同上
 * 				this.ajax.get(url,[data],[callback],[config])	固定请求方式为get,其他使用注意同上
 * 				this.ajax.post(url,[data],[callback],[config])	固定请求方式为post,其他使用注意同上
 * 所有的api组放在commonapi中，如需拆分api文件在commonapi依次引入即可，如需动态载入子模块api方法使用ajax.initapi(apiConfigList)进行手动加载
 * @Author   T 175588516@qq.com
 * 典型应用示例
 * async ()=>{
 * 		let rs = await this.ajax({id:2});
 * 		console.log(rs);
 * }
 * 接口配置信息示例 ajax.initapi([config])
 * @Params method 请求方式
 * @Params url 请求地址
 * @Params desc 请求说明
 * @Params mock mock地址
 * @Params dontRrefreshToken 不需要token刷新
 * @Params dontUseToken 接口不需要传递token进行账户登录校验
 * @Params passSuccessful 直接放行响应成功 default:false 直接放行的接口不走拦截器
 * @Params passLose 直接放行响应失败 default:false 直接放行的接口不走拦截器
 * 加密功能
 * @Params encipher:false,//启用加密 （传输数据加密 不含一维字段名 二维以上对象加密过程自动转一维）
 * @Params encipherKey:false,//字段名加密
 * @Params decrypt:false,//启用解密
 * @Params decryptKey:false,//字段名需要解密
 * 防抖（仅时间范围内最后一个执行）和节流（仅时间范围内第一个执行） 可用于防重复提交功能  可同时使用但是不建议  节流防抖时间不计算在api的timeout内
 * @Params debounceTime:0  default-0 默认防抖时间为0，即为不需要，配置单位为ms,此处配置建议值：300，根据实际情况调整  建议使用在数据搜索请求，动态下拉动态检索等
 * @Params debounceKey 自定义防抖标识  默认为接口url 可以自定义为任意名称，也可多接口使用同一名称实现优先进入队列获取优先请求权限，也可将url+参数序列化之后的字符实现接口不重复参数请求
 * @Params throttleTime:0  default-0 默认节流时间为0，即为不需要，配置单位为ms,配置建议值：800，根据实际情况调整  建议使用在数据提交请求畅姐
 * @Params throttleKey 自定义节流标识  作用和使用方式同上
 * @Params confirm 请求toast.confirmg配置  默认为false  配置示例：{...confirm配置}
 * @Params loading 请求toast.loading配置  默认为false  配置示例：{status:Ref(loadingBool),inst:Ref(loadingInst),...loading配置}
 *
 * config扩展了urlRewrite(url)方法 ，用于在请求发起前对url进行重写，可自定义重写规则，如：url重写后缀，url重写参数，url重写参数值等
 * 示例ajax.apiFunName(data,{urlRewrite:url=>url+'/123123'})
 *
 * config扩展 toast.loading和toast.confirm支持
 * ajax.apiFunName(data,{
 *  confirm:{
 * 		...confirm配置
 * 	},
 * 	loading:{
 * 		status:Ref(loadingBool),//loading状态
 * 		inst: Ref(loadingInst),//loading实例  仅未销毁前有效
 * 		...loading配置
 *  },
 * 注：不推荐前端请求方式下载，建议走浏览器原生下载，体验更好。
 * @Params downLoad 自动转成blob下载 存在该对象就视为启动  建议启用passSuccessful 且timeout设置大一点默认20s  当前仅处理支持返回文件流；如需其他格式支持如:base64,对象深层数据转存文件请@木木 ；后期可以去处理为全局下载，支持断点续传，缓存下载进度离线刷新不丢失进度？（待开发）
 * downLoad:{
 * 	fileName:'文件名',//强制下载文件名  默认取接口返回的文件名headers['content-disposition']
 * }
 * @Params scheme:{rules:{},...} （待开发） 申明式请求前校验支持，支持聚焦
 * })
 */

import apilis from "@/api/commonApi.js";
import cache from './cache.js';
import axios from "axios";
import store from "@/store";
import router from "@/router";
import conf from "@/config/index"
import * as crypto from "./crypto/index.js";
import ThorttleAndDebounce from "./axios-thorttleAndDebounce.js";
import { ElMessage } from 'element-plus'
const TOKEN_INVALID = 'Token认证失败，请重新登录'
const NETWORK_ERROR = '网络请求异常，请稍后重试'
var inst = axios.create({
  timeout: 20000,
});

// inst.defaults.baseURL='/adminctr/';
// inst.defaults.baseURL='https://localhost:8200/adminctr/';
inst.defaults.baseURL = conf.baseApi;

let ReauestFilter = new ThorttleAndDebounce(axios); //防抖和节流实现类初始化

/**
 * 重构的请求核心方法
 * @param {*} api 接口配置信息  至少需要包含url和method
 * @param {*} data 接口数据
 * @param {*} config 接口发起式的私有配置
 * @param {*} callback 接口请求回调
 * @returns  存在请求回调则执行回调返回 否则使用返回promise结果
 */
async function send(api, data = {}, config = {}, callback) {
  let lcf = await useLoadingConfirm(api, config);
  if (!lcf) {
    if (callback) callback({});
    return {};
  }
  config = {
    ...api,
    dontRrefreshToken: api.dontRrefreshToken || false,
    dontUseToken: api.dontUseToken || false,
    ...config,
  };
  let url = config.url || config.moclurl;
  url = config.urlRewrite ? config.urlRewrite(url) : url;
  let response;
  try {
    if (["post", "put", "patch"].includes(config.method)) {
      response = await inst[config.method](url, data, config);
    } else {
      config.params = {
        ...(config.params || {}),
        ...data,
      };
      response = await inst[config.method](url, config);
    }
  } catch (err) {
    // console.log(err);
    response = err;
  }
  // console.log(response);
  destoryLoading(lcf.loading);
  if (callback) callback(response);
  return response;
}

/**
 * 接口confirm和loading
 * @param {*} api
 * @param {*} config
 * @returns
 */
async function useLoadingConfirm(api, config) {
  let confirm =
    api.confirm || config.confirm
      ? { ...(api.confirm || {}), ...(config.confirm || {}) }
      : false;
  let loading =
    api.loading || config.loading
      ? { ...(api.loading || {}), ...(config.loading || {}) }
      : false;
  if (confirm && !(await Toast.confirm(confirm.message))) return false;
  if (loading) {
    if (!loading.status) loading.status = { value: true };
    else loading.status.value = true;
    if (!loading.inst) loading.inst = { value: Toast.loading(loading) };
    else loading.inst.value = Toast.loading(loading);
  }
  return { loading, confirm };
}
/**
 * 注销loading
 * @param {*} loading loading配置
 */
function destoryLoading(loading) {
  if (loading?.status?.value) {
    loading.status.value = false;
    loading.inst.value && loading.inst.value.destory();
    loading.inst.value = null;
  }
}

/**
 * 通用方法  可以进行普通的异步
 * @param  {[type]}   url      url路径
 * @param  {[type]}   config   数据对象
 * @param  {Function} callback 回调函数  有回调为异步  无回调为同步,需等待结果
 * @return {[type]}            response  异步时无return
 */
function ajax(url, config = {}, callback) {
  return send(
    {
      url,
      method: "post",
    },
    {},
    config,
    callback
  );
}
ajax.api = apilis;
ajax.inst = inst;
/**
 * get请求
 * @param  {[type]}   url      url路径
 * @param  {[type]}   data     数据对象
 * @param  {Function} callback 回调函数  有回调为异步  无回调为同步,需等待结果
 * @return {[type]}            response  异步时无return return1个请求token用于后面取消？
 */
ajax.get = (url, data = {}, callback, config = {}) =>
  send({ url, method: "get" }, data, config, callback);
/**
 * post请求
 * @param  {[type]}   url      url路径
 * @param  {[type]}   data     数据对象
 * @param  {Function} callback 回调函数  有回调为异步  无回调为同步,需等待结果
 * @return {[type]}            response 异步时无return
 */
ajax.post = (url, data = {}, callback, config = {}) =>
  send({ url, method: "post" }, data, config, callback);

ajax.put = (url, data = {}, callback, config = {}) =>
  send({ url, method: "put" }, data, config, callback);

/**
 * 加载api和预处理方法
 * @param {*} useobj 方法挂载对象
 * @param {*} apilis  所有为系统配置好的默认请求
 * @param {*} conf  系统请求默认conf
 */
function apiListReady(useobj, apilis, conf = {}) {
  for (let k in apilis) {
    let api = apilis[k];
    if (!!useobj[k]) {
      console.warn("接口被重复定义", api);
      continue;
    }

    useobj[k] = async (data = {}, config = {}, callback) => {
      return send(api, data, { ...conf, ...config }, callback);
    };
  }
}
// 载入系统默认api挂载到主方法
apiListReady(ajax, apilis);

/**
 * 动态载入其他模块私有api
 * @param {*} mylist
 * @param {*} conf 创建示例时设置该实例的基础config
 * @returns
 */
ajax.initapi = function (mylist, conf = {}) {
  let myapi = { mylist };
  apiListReady(myapi, mylist, conf);
  myapi.inst = inst;
  myapi.get = ajax.get;
  myapi.post = ajax.post;
  return myapi;
};
// test 尝试删除lisa1 cookie
function deleteCookie(name, path = "") {
  document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:01 GMT;path=${path};domain=.eiceducation.com.cn`;
}
// 请求拦截器
inst.interceptors.request.use(
  async (config) => {
    //  console.log(config.url)
    deleteCookie("eic.session", "/");
    // 先用防抖和节流拦截
    await ReauestFilter.repetition(config);
    // 判断哪些接口需要等待接口完成刷新
    if(!config.dontRrefreshToken){
    	await store.dispatch('waitRfToken')
    }
   
    if (!config.dontUseToken) {
        const token=store.state.userInfo.token
        config.headers[`${conf.tokenKey}`] = conf.tokenKeyType?`${conf.tokenKeyType} ${token}`:token;
    }
    if ( 
      config.method == "get" ||
      config.method == "delete" ||
      config.method == "options"
    ) {
    } else {
      try {
        let encipher =
          config.encipher == undefined ? conf.encipher : config.encipher;
        let encipherKey =
          config.encipherKey == undefined
            ? conf.encipherKey
            : config.encipherKey;
        let decryptKey =
          config.decryptKey == undefined ? conf.decryptKey : config.decryptKey;
        if (config.data && encipher) {
          config.data = crypto.encryption(config.data, encipherKey);
          // console.log('config.data',crypto.decryption(config.data,decryptKey),'to',config.data)
        }
      } catch (e) {
        console.warn("序列失败", e);
      }
    }
    return config;
  },
  (err) => {
    console.log("请求失败", err.config);
    if (err.config.passSuccessful) return err;
    // 请求错误 未找到目标服务器
    let error = {
      status: 0,
      code: 0,
      msg: "请求目标服务器或网络异常",
    };
    if (err.response) {
      error.code = err.response.status;
    }
    Toast.err(error.msg);
    return Promise.reject(error);
  }
);
// 响应拦截器
inst.interceptors.response.use(
  (res) => {
    // downLoadFile(res)
    if (res.config.passSuccessful) return res;
    const { code, message, error, msg } = res.data;
    // errors 用作开发错误信息 不提示出来  message error msg作为提示信息弹出
    console.log(code);
    switch (code) {
      case 200:
        // 只有状态200直接通过  其他的code都当错误提示（特定状态除外）
        break;
      case 401:
          ElMessage.error(TOKEN_INVALID)
          router.push({
            path: "/login",
          });
        break;
      default:
        ElMessage.error(error || message || msg);
    }
    return res.data.data;
  },
  (err) => {
    console.log("响应失败：", err);
    if (err.config.passLose) return Promise.reject(err);
    // 请求失败 已找到目标服务器 但是服务未能正确处理请求
    let error = {
      code: 0,
    };
    if (err.response) {
      switch (err.response.status) {
        case 404:
        case 405:
          error.msg = "请联系管理员";
          break; //目标服务器已拒绝本次响应
        case 500:
          error.msg = "请联系管理员";
          break; //目标服务器响应出错
        case 502:
          error.msg = "请联系管理员";
          break; //目标服务器无响应
        case 401:
          ElMessage.error(TOKEN_INVALID)
          router.push({
            path: "/login",
          });
          // goLogin(true);
        
        default:
          error.msg =
            err.response.data.message ||
            err.response.statusText ||
            err.response.data.errors;
      }
      error.code = err.response.status;
    } else {
      error.code = 0;
      error.msg = "网络连接失败,请稍后重试"; //目标服务器响应出错
    }
    console.log("请求失败", error);
    Toast.err(error.msg);
    return Promise.reject(error);
  }
);

/**
 * 跳转到登录页面
 */
function goLogin(reload) {
  // 	const path = router.currentRoute.path;
  let user = JSON.parse(JSON.stringify(store.state.user));
  user.token = "";
  store.dispatch("saveUser", user);
  router.push({
    path: "/login",
  });

  // if (reload) {
  // 	// location.replace(process.envBASE_URL+'/login'); // 这样跳转避免再次登录重复注册动态路由
  // 	window.location.href = process.env.BASE_URL + "/login"
  // } else {
  // 	const path = router.currentRoute.path;
  // 	router.push({
  // 		path: '/login',
  // 		query: path && path !== '/' ? {
  // 			form: path
  // 		} : null
  // 	});
  // }
}

/**
 * 文件下载支持
 * @param {*} res axios响应拦截内容
 * @returns
 */
function downLoadFile(res) {
  if (!res.config.downLoad || res.status != 200) return res;
  const path = URL.createObjectURL(res.data);
  const fileName = decodeURI(
    res.headers["content-disposition"].replace("attachment;filename=", "")
  );
  let el = document.createElement("a");
  el.download = res.config.downLoad.fileName || fileName;
  el.href = path;
  el.click();
  URL.revokeObjectURL(path);
  el = null;
  return res;
}

export default ajax;
